Public Module LastfmScrobbler

    Private UserName As String
    Private Password As String
    Private AlreadyHandshaked As Boolean
    Private ClientId As String = "mem"
    Private ClientVersion As String = "0.9.6.5"
    Private SessionID As String
    Private NowPlayingURL As String
    Private SubmissionURL As String
    Public ProvadoUltimoLastfmUsernameAndPasswordAndFailed As Boolean = False
    Public ProvadoUltimoLastfmUsernameAndPassword As Boolean = False
    Public AlreadyWarnedAboutUpdateApplication As Boolean = False
    Public AlreadyWarnedAboutSystemClock As Boolean = False
    Public IsTryingToHandshake As Boolean = False
    Public MaxTracksToSubmitAtOnce As Integer = 25
    Public Log As String
    Public Event ProcessEnded()
    Dim WithEvents BWSubmit As New System.ComponentModel.BackgroundWorker
    Public ScrobblingCache As SortedDictionary(Of Long, BasicSongData)
    Private CheckConn As New CheckInternetConnection()

    Public Function GetUnixTimeSpamp(ByVal Fecha As Date) As Long
        Dim FechaBase As New DateTime(1970, 1, 1, 0, 0, 0, 0)
        Dim span As TimeSpan = Fecha.ToUniversalTime() - FechaBase
        Return CLng(span.TotalSeconds)
    End Function
    Public HandshackeHardFailureCounter As Integer
    Public HardFailureCounter As Integer

    Public Function Handshake(ByVal userName As String, ByVal password As String, ByRef HandshakeHardFailure As Boolean) As Boolean

        HandshakeHardFailure = False

        If ProvadoUltimoLastfmUsernameAndPasswordAndFailed Then
            RaiseEvent ProcessEnded()
            Return False
        End If
        If AlreadyWarnedAboutUpdateApplication Then
            RaiseEvent ProcessEnded()
            Return False
        End If
        If AlreadyWarnedAboutSystemClock Then
            RaiseEvent ProcessEnded()
            Return False
        End If

        LastfmScrobbler.UserName = userName
        LastfmScrobbler.Password = password


        Dim Timespamp As Long = GetUnixTimeSpamp(Now)
        Dim handshakeURL As String = "http://post.audioscrobbler.com/?hs=true&" & _
        "p=1.2.1&c=" & ClientId & "&v=" & ClientVersion & "&u=" & LastfmScrobbler.UserName & _
        "&t=" & Timespamp & "&a=" & MD5Hash(LastfmScrobbler.Password & Timespamp)


        Dim req As Net.HttpWebRequest = CType(Net.WebRequest.Create(handshakeURL), Net.HttpWebRequest)
        Dim res As Net.HttpWebResponse = Nothing

        Try
            res = CType(req.GetResponse, Net.HttpWebResponse)
            Dim response As IO.StreamReader = New IO.StreamReader(res.GetResponseStream, System.Text.Encoding.UTF8)
            Dim LineNumber As Integer = 0
            Dim Exitoso As Boolean = False

            Do
                Dim line As String
                line = response.ReadLine
                Select Case LineNumber
                    Case 0
                        Select Case line
                            Case "OK"
                                Exitoso = True
                                HandshackeHardFailureCounter = 0
                                HardFailureCounter = 0
                                Log &= vbCrLf & "Authentication Successfull with username: " + LastfmScrobbler.UserName + ": OK"
                            Case "BANNED"
                                If AlreadyWarnedAboutUpdateApplication = False Then
                                    'MsgBox("Please update the program to the latest version, apparently it has been banned from last.fm")
                                End If
                                AlreadyWarnedAboutUpdateApplication = True
                                Log &= vbCrLf & "Authentication Unsuccessfull please update you MeaMod Playme : BANNED"
                            Case "BADAUTH"
                                If ProvadoUltimoLastfmUsernameAndPasswordAndFailed = False Then
                                    'MsgBox("Please check your last.fm username and password")
                                End If
                                ProvadoUltimoLastfmUsernameAndPasswordAndFailed = True
                                Log &= vbCrLf & "Authentication Unsuccessfull with username: " + LastfmScrobbler.UserName + ": BADAUTH"
                            Case "BADTIME"
                                If AlreadyWarnedAboutSystemClock = False Then
                                    'MsgBox("Please correct your system clock and restart the program, apparently it's not close enough to current time")
                                End If
                                AlreadyWarnedAboutSystemClock = True
                                Log &= vbCrLf & "Authentication Unsuccessfull you system clock is not the correct time : BADTIME"
                            Case Else 'incluye FAILED <reason> , y otras cosas desconocidas
                                HandshakeHardFailure = True
                                HandshackeHardFailureCounter += 1
                                Log &= vbCrLf & "Authentication Unsuccessfull : ERROR"
                        End Select
                    Case 1
                        If Exitoso = True Then
                            SessionID = line
                        End If
                    Case 2
                        If Exitoso = True Then
                            NowPlayingURL = line
                        End If
                    Case 3
                        If Exitoso = True Then
                            SubmissionURL = line
                        End If
                End Select
                LineNumber += 1
                If line = "" Then Exit Do
            Loop

            ProvadoUltimoLastfmUsernameAndPassword = True
            response.Close()

            If Exitoso Then
                RaiseEvent ProcessEnded()
                Return True
            Else
                RaiseEvent ProcessEnded()
                Return False
            End If

        Catch e As Exception
            RaiseEvent ProcessEnded()
            Return False
        End Try
        RaiseEvent ProcessEnded()
    End Function
    Public Sub WaitIfHandshakeHardFailure(ByVal HardFailure As Boolean)
        If HardFailure Then
            Dim delay As Integer
            Try
                delay = CInt(2 ^ (HandshackeHardFailureCounter - 1))
            Catch ex As Exception
                delay = 120
            End Try
            If delay > 120 Then
                delay = 120
            End If
            If delay <= 0 Then
                delay = 1
            End If
            System.Threading.Thread.Sleep(delay * 60 * 1000)
        End If
    End Sub
    Public Function HandshakeCurrentUser() As Boolean

        If HardFailureCounter >= 3 Then
            AlreadyHandshaked = False 'repetir el handshacke
        End If

        If AlreadyHandshaked = False Or ProvadoUltimoLastfmUsernameAndPassword = False Then
            Dim HardFailure As Boolean = False
            If LastfmScrobbler.Handshake(UserName, Password, HardFailure) = True Then
                AlreadyHandshaked = True
                Return True
            Else
                WaitIfHandshakeHardFailure(HardFailure)
                AlreadyHandshaked = False
                Return False
            End If
        Else
            'TANTO AlreadyHandshaked COMO ProvadoUltimoLastfmUsernameAndPassword SON TRUE, O SEA QUE TODO VA BIEN
            Return True
        End If
    End Function

    Dim WithEvents BWSendNowPlayingNotification As New System.ComponentModel.BackgroundWorker
    Dim SendingNowPlayingNotification As Boolean = False
    Dim LastDateTimeSendedNowPlayingNotification As Date = Date.MinValue

    Public Sub SendNowPlayingNotification(ByVal SongData As BasicSongData)
        If (Now - LastDateTimeSendedNowPlayingNotification).TotalSeconds < 15 Then
            Exit Sub
            'esto porque se bloqueaba al mandarlos muy seguidos
        End If

        LastDateTimeSendedNowPlayingNotification = Now
        General.RunWorkerAsyncIfNotBusy(BWSendNowPlayingNotification, SongData)
    End Sub

    Private Sub BWSendNowPlayingNotification_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BWSendNowPlayingNotification.DoWork
        If SendingNowPlayingNotification = True Then
            Exit Sub
        End If
        SendingNowPlayingNotification = True

        If UserName = "" Or CheckConn.IsConnected = False Then Exit Sub


        Dim SongData As BasicSongData = CType(e.Argument, BasicSongData)
TryAgain:
        If HandshakeCurrentUser() Then
            Dim TrackNumber As Integer = CInt(Val(SongData.TrackNumber)) 'esto para que no envie al last.fm los ceros esos 

            Dim NowPlayingNotificationURL As String = NowPlayingURL & _
                "?s=" & SessionID & "&a=" & URLEncode(SongData.Artist) & _
                "&t=" & URLEncode(SongData.TrackName) & "&b=" & URLEncode(SongData.Album) & _
                "&l=" & SongData.LengthInSeconds & "&n=" & TrackNumber & "&m="
            Try
                Dim req As Net.HttpWebRequest = CType(Net.WebRequest.Create(NowPlayingNotificationURL), Net.HttpWebRequest)
                Dim res As Net.HttpWebResponse = Nothing
                req.Method = "POST"
                req.Timeout = 10000
                req.KeepAlive = False
                res = CType(req.GetResponse, Net.HttpWebResponse)
                Dim response As IO.StreamReader = New IO.StreamReader(res.GetResponseStream, System.Text.Encoding.UTF8)
                Dim line As String
                line = response.ReadLine
                Select Case line
                    Case "OK"
                        'do nothing todo bien.
                        HardFailureCounter = 0
                        Log &= vbCrLf & SongData.Artist & " - " & SongData.TrackName & " Now Playing Notification Sent Succesfully"
                    Case "BADSESSION"
                        'This indicates that the Session ID sent was somehow invalid, possibly because another client has performed a handshake for this user. On receiving this, the client should re-handshake with the server before continuing. 
                        AlreadyHandshaked = False
                        Log &= vbCrLf & "Error Sending Now Playing Notification " & SongData.Artist & " - " & SongData.TrackName & ": BADSESSION"
                        GoTo TryAgain
                    Case Else
                        HardFailureCounter += 1
                        Log &= vbCrLf & "Error Sending Now Playing Notification " & SongData.Artist & " - " & SongData.TrackName & ": Other Error"
                End Select
            Catch ex As Exception
                HardFailureCounter += 1
                Log &= vbCrLf & "Error Sending Now Playing Notification " & SongData.Artist & " - " & SongData.TrackName & ": Unknown Exception"
            End Try
        Else
            'do nothing
        End If
        SendingNowPlayingNotification = False
        RaiseEvent ProcessEnded()
    End Sub

    Public Sub SerializarScrobblingCache()
        SerializeToFile(Of SortedDictionary(Of Long, BasicSongData))(System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\" + My.Application.Info.CompanyName + "\" + My.Application.Info.AssemblyName, "ScrobblingPendingQueue"), ScrobblingCache)
    End Sub
    Public Sub DesSerializarScrobblingCache()
        Dim p As String = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\" + My.Application.Info.CompanyName + "\" + My.Application.Info.AssemblyName, "ScrobblingPendingQueue")
        If IO.File.Exists(p) Then
            Try
                ScrobblingCache = DeSerializeFromFile(Of SortedDictionary(Of Long, BasicSongData))(p)
            Catch ex As Exception
                'MsgBox("ScrobblingPendingQueue File Load Failed")
                Log &= vbCrLf & "ScrobblingPendingQueue File Load Failed : ERROR"
                ScrobblingCache = New SortedDictionary(Of Long, BasicSongData)
            End Try
        Else
            ScrobblingCache = New SortedDictionary(Of Long, BasicSongData)
        End If
        RaiseEvent ProcessEnded()
    End Sub

    Public Sub Scrobble(ByVal SongData As BasicSongData)
        If UserName <> "" Then

            If BWSubmit.IsBusy Then
                ScrobblingCache.Add(SongData.UnixTimestampTrackStartedPlaying, SongData)
            Else
                If CheckConn.IsConnected = True Then
                    BWSubmit.RunWorkerAsync(SongData)
                Else
                    ScrobblingCache.Add(SongData.UnixTimestampTrackStartedPlaying, SongData)
                End If
            End If
        Else
        End If
    End Sub

    Private Sub BWSubmit_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BWSubmit.DoWork

        Dim SongData As BasicSongData = CType(e.Argument, BasicSongData)
tryagain:
        If HandshakeCurrentUser() Then
            If Not ScrobblingCache.ContainsKey(SongData.UnixTimestampTrackStartedPlaying) Then
                ScrobblingCache.Add(SongData.UnixTimestampTrackStartedPlaying, SongData)
            Else
                'si ya la contenia no entiendo porque podria pasar pero de todas maneras se ignora y se itenta agregar las que esten en la fila en este momento
            End If

            Try
                'Primero agrega la song a la queue
                Dim Limit As Integer = ScrobblingCache.Count
                If Limit > MaxTracksToSubmitAtOnce Then
                    Limit = MaxTracksToSubmitAtOnce
                End If


                Dim ScrobblingURL As String = SubmissionURL & "?s=" & SessionID
                Dim KeysToRemoveIfExitoso As New List(Of Long)
                Dim count As Integer = 0

                For Each kvp As KeyValuePair(Of Long, BasicSongData) In ScrobblingCache
                    count += 1
                    If count > Limit Then
                        Exit For
                    End If


                    Dim SongAScrobblear As BasicSongData = kvp.Value


                    Dim TrackNumber As Integer = CInt(Val(SongAScrobblear.TrackNumber)) 'esto para que no envie al last.fm los ceros esos 
                    ScrobblingURL &= String.Format("&a[{0}]=" & URLEncode(SongAScrobblear.Artist) & _
                                                   "&t[{0}]=" & URLEncode(SongAScrobblear.TrackName) & _
                                                   "&i[{0}]=" & SongAScrobblear.UnixTimestampTrackStartedPlaying & _
                                                   "&o[{0}]=P" & _
                                                   "&r[{0}]=" & _
                                                   "&l[{0}]=" & SongAScrobblear.LengthInSeconds & _
                                                   "&b[{0}]=" & URLEncode(SongAScrobblear.Album) & _
                                                   "&n[{0}]=" & TrackNumber & _
                                                   "&m[{0}]=", count - 1)

                    KeysToRemoveIfExitoso.Add(kvp.Key)
                Next





                Dim req As Net.HttpWebRequest = CType(Net.WebRequest.Create(ScrobblingURL), Net.HttpWebRequest)
                Dim res As Net.HttpWebResponse = Nothing
                req.Method = "POST"
                req.KeepAlive = False
                res = CType(req.GetResponse, Net.HttpWebResponse)
                Dim response As IO.StreamReader = New IO.StreamReader(res.GetResponseStream, System.Text.Encoding.UTF8)
                Dim line As String

                line = response.ReadLine

                Select Case line
                    Case "OK"
                        'excelente todo fue enviado bien remover todos los items enviados de la lista!!
                        For Each keytoremove As Long In KeysToRemoveIfExitoso
                            ScrobblingCache.Remove(keytoremove)
                        Next
                        HardFailureCounter = 0
                        Log &= vbCrLf & SongData.Artist & " - " & SongData.TrackName & " Scrobbled Succesfully"
                    Case "BADSESSION"
                        AlreadyHandshaked = False 'para la proxima vuelve y envia peticion
                        Log &= vbCrLf & "Error Scrobbling " & SongData.Artist & " - " & SongData.TrackName & ": BADSESSION"
                        GoTo tryagain
                    Case Else
                        'failed y otros
                        Log &= vbCrLf & "Error Scrobbling " & SongData.Artist & " - " & SongData.TrackName & ": Other Error"
                        HardFailureCounter += 1
                End Select
            Catch ex As Exception
                'do nothing ya la habia agregado
                Log &= vbCrLf & "Error Scrobbling " & SongData.Artist & " - " & SongData.TrackName & ": Unknown Exception"
                HardFailureCounter += 1
            End Try
        Else
            Try
                ScrobblingCache.Add(SongData.UnixTimestampTrackStartedPlaying, SongData)
            Catch ex As Exception
                Log &= vbCrLf & "Error Scrobbling " & SongData.Artist & " - " & SongData.TrackName & " to cache : Unknown Exception"
            End Try
            AlreadyHandshaked = False 'para la proxima vuelve y envia peticion
        End If
        RaiseEvent ProcessEnded()
    End Sub
End Module
